#include "model.h"
#include "value.h"
#include "z_logger.h"
#include "../iec60870/iec101.h"
#include "../iec60870/apdu.h"

static uint8_t *_do_qds(uint8_t *ptr, type_id_101_e type_id, value_t *value) {
    info_value_q_t *info_value = (info_value_q_t *) ptr;
    switch (type_id) {
        case M_SP_NA_1:
        case M_SP_TA_1:
        case M_SP_TB_1:
            value->bval = info_value->qds.spi;
            break;
        case M_DP_NA_1:
        case M_DP_TA_1:
        case M_DP_TB_1:
            value->bval = info_value->qds.dpi;
            break;
        default:
            break;
    }
    return ptr + sizeof(info_value_q_t);
}

static uint8_t *_do_i1(uint8_t *ptr, type_id_101_e type_id, value_t *value) {
    info_value_i1_t *info_value = (info_value_i1_t *) ptr;
    value->ival = info_value->val;
    return ptr + sizeof(info_value_i1_t);
}

static uint8_t *_do_i2(uint8_t *ptr, type_id_101_e type_id, value_t *value) {
    info_value_i2_t *info_value = (info_value_i2_t *) ptr;
    value->ival = info_value->sval;
    return ptr + sizeof(info_value_i2_t);
}

static uint8_t *_do_i4(uint8_t *ptr, type_id_101_e type_id, value_t *value) {
    info_value_i4_t *info_value = (info_value_i4_t *) ptr;
    value->ival = info_value->ival;
    return ptr + sizeof(info_value_i4_t);
}

static uint8_t *_do_cp24time2a(uint8_t *ptr, value_t *value) {
    cp24time2a_t *cp24 = (cp24time2a_t *) ptr;
    value->timestamp = get_cp24time2a(cp24);
    return ptr + sizeof(cp24time2a_t);
}

static uint8_t *_do_cp56time2a(uint8_t *ptr, value_t *value) {
    cp56time2a_t *cp56 = (cp56time2a_t *) ptr;
    value->timestamp = get_cp56time2a(cp56);
    return ptr + sizeof(cp56time2a_t);
}

static void _handle_info_value(value_id_t *id, value_t *value) {
    char key[64], val[128];
    sprintf(key, "%d_%d_%d_%d", id->chn_id, id->dev_id, id->seg_id, id->tag_id);
    value_printf(val, value);

    log_trace("%s={%s}", key, val);
}

void _handle_iec101_apdu(void *apdu, channel_t *channel, device_t *device) {
    int i;
    uint8_t *ptr = ((apdu_t *) apdu)->var.data;
    asdu_dui_t *asdu = (asdu_dui_t *) ptr;
    ptr += sizeof(asdu_dui_t);

    for (i = 0; i < asdu->vsq.num; ++i) {
        value_id_t id;
        value_t value;

        if (0 == i || sq_single == asdu->vsq.sq) {
            info_obj_addr_t *addr = (info_obj_addr_t *) ptr;
            ptr += sizeof(info_obj_addr_t);
            id.tag_id = addr->tag;
        } else {
            id.tag_id++;
        }

        switch ((type_id_101_e) asdu->type_id) {
            case M_SP_NA_1:
            case M_SP_TA_1:
            case M_SP_TB_1:
            case M_DP_NA_1:
            case M_DP_TA_1:
            case M_DP_TB_1:
                ptr = _do_qds(ptr, asdu->type_id, &value);
                break;
            case M_ST_NA_1:
            case M_ST_TA_1:
            case M_ST_TB_1:
                ptr = _do_i1(ptr, asdu->type_id, &value);
                break;
            case M_ME_NA_1://normalized
            case M_ME_TA_1:
            case M_ME_NB_1://scaled
            case M_ME_TB_1:
            case M_ME_TD_1:
            case M_ME_TE_1:
                ptr = _do_i2(ptr, asdu->type_id, &value);
                break;
            case M_ME_NC_1://float
            case M_ME_TC_1:
            case M_ME_TF_1:
            case M_IT_NA_1://totals
            case M_IT_TA_1:
            case M_IT_TB_1:
            case M_BO_NA_1://bitstring
            case M_BO_TA_1:
            case M_BO_TB_1:
                ptr = _do_i4(ptr, asdu->type_id, &value);
                break;
            default:
                break;
        }

        switch ((type_id_101_e) asdu->type_id) {
            case M_SP_TA_1:
            case M_DP_TA_1:
            case M_ME_TA_1:
            case M_ME_TB_1:
            case M_ME_TC_1:
            case M_IT_TA_1:
                ptr = _do_cp24time2a(ptr, &value);
                break;
            case M_SP_TB_1:
            case M_DP_TB_1:
            case M_ME_TD_1:
            case M_ME_TE_1:
            case M_ME_TF_1:
            case M_IT_TB_1:
                ptr = _do_cp56time2a(ptr, &value);
                break;
            default:
                break;
        }

        _handle_info_value(&id, &value);
    }
}